package org.gwt.grin.client.features;

import java.util.Map;
import java.util.Set;

import org.gwt.grin.client.Feature;
import org.gwt.grin.client.util.Graphics;

/**
 * Represents a group of features that are all activated at the same time. It's
 * useful to group features together so that they can be turned on and off as a
 * unit within an assembly.
 * 
 * @author Bill Foote (http://jovial.com)
 * @author ggeorg
 */
public class Group extends Feature {

	/**
	 * The partsof this group as present in the original scene graph.
	 */
	private Feature[] parts;

	/**
	 * The parts that are currently visible within this group. This will be
	 * identical to parts, unless it has been modified by the application with
	 * resetVisibleParts().
	 * 
	 * See the documentation of resetVisibleParts().
	 */
	private Feature[] visibleParts;

	private boolean activated = false;

	// Number of features checked so far for needsMoreSetup()
	private int numSetupChecked;

	public Group(String name) {
		super(name);
	}
	
	/**
	 * Set the parts that make up this group. This may only be called when the
	 * object is initially being populated.
	 * 
	 * @param parts
	 *            the parts that make up this group
	 */
	public void setup(Feature[] parts) {
		this.parts = this.visibleParts = parts;
	}

	@Override
	protected Feature createClone(Map<Feature, Feature> clones) {
		if (!isSetup() || activated) {
			throw new IllegalStateException();
		}
		Group result = new Group(null);
		result.show = show; // FIXME
		result.parts = new Feature[parts.length];
		result.numSetupChecked = numSetupChecked;
		for (int i = 0; i < parts.length; i++) {
			result.parts[i] = parts[i].makeNewClone(clones);
		}
		// result.activated remains false
		return result;
		// No initializeClone() of this feature is needed.
	}

	@Override
	protected void initializeClone(Feature original,
			Map<Feature, Feature> clones) {
		super.initializeClone(original, clones);
		Group other = (Group) original;
		if (other.visibleParts == other.parts) {
			visibleParts = parts;
		} else {
			visibleParts = new Feature[other.visibleParts.length];
			for (int i = 0; i < visibleParts.length; i++) {
				Feature f = other.visibleParts[i];
				visibleParts[i] = Feature.clonedReference(f, clones);
			}
		}
	}

	@Override
	public void addSubgraph(Set<Feature> set) {
		if (set.contains(this)) {
			return; // Avoid O(n^2) with assemblies
		}
		super.addSubgraph(set);
		for (int i = 0; i < parts.length; i++) {
			parts[i].addSubgraph(set);
		}
	}

	/**
	 * Get the parts that make up this group in the original scene graph.
	 * 
	 * @return the parts of the original scene graph
	 * @return
	 */
	public Feature[] getParts() {
		return parts;
	}

	@Override
	public int getX() {
		int x = Integer.MAX_VALUE;
		for (int i = 0; i < visibleParts.length; i++) {
			int val = visibleParts[i].getX();
			if (val < x) {
				x = val;
			}
		}
		return x;
	}

	@Override
	public int getY() {
		int y = Integer.MAX_VALUE;
		for (int i = 0; i < visibleParts.length; i++) {
			int val = visibleParts[i].getY();
			if (val < y) {
				y = val;
			}
		}
		return y;
	}

	@Override
	protected void initialize() {
		// The show will initialize our sub-features, so we don't need to do
		// anything here.
	}

	@Override
	protected void destroy() {
		// The show will destroy our sub-features, so we don't need to do
		// anything here.
	}

	/**
	 * Re-sets the parts that are visible in this group to a new set. This
	 * method may be used to make features created with
	 * {@link #cloneSubgraph(Map)} visible. Indeed, with the features built into
	 * GRIN, this is the only way to display a cloned feature. Such features
	 * don't need to be set up in the way that normal features are (via
	 * {@link #setup()} which is balanced by {@link #unsetup()}, but they do
	 * need to be cloned from features that have been set up.
	 * <p>
	 * This method may be called by applications, but it must only be called
	 * within a command body or inside of {@link Director#notifyNextFrame()}.
	 * <p>
	 * If called with a non-null argument, then this group must be in the set up
	 * state. If the argument is {@code null} and we're not set up, then we must
	 * also not be activated.
	 * <p>
	 * If this group node is activated, then the new child features of this node
	 * will be de-activated.
	 * <p>
	 * This method relies on {@link #resetVisiblePartsNoAssert(Feature[])} after
	 * performing appropriate parameter and state checks.
	 * 
	 * @param visibleParts
	 *            an array of parts (a value of {@code null} re-sets this group
	 *            to its original state)
	 * 
	 * @see #cloneSubgraph(Map)
	 * @see #resetVisiblePartsNoAssert(Feature[])
	 */
	public void resetVisibleParts(Feature[] visibleParts) {
		// TODO asserts

		resetVisiblePartsNoAssert(visibleParts);
	}

	/**
	 * Re-sets the parts that are visible in this group to a new set without
	 * performing any of the assertion checks.
	 * 
	 * @param visibleParts
	 *            an array of parts (a value of {@code null} re-sets this group
	 *            to its original state)
	 * 
	 * @see #resetVisibleParts(Feature[])
	 */
	public void resetVisiblePartsNoAssert(Feature[] visibleParts) {
		if (visibleParts == null) {
			visibleParts = parts;
		}
		if (activated) {
			for (int i = 0; i < visibleParts.length; i++) {
				visibleParts[i].activate();
			}
			for (int i = 0; i < this.visibleParts.length; i++) {
				this.visibleParts[i].deactivate();
			}
		}
		this.visibleParts = visibleParts;
	}

	@Override
	protected void setActivateMode(boolean mode) {
		activated = mode;
		if (mode) {
			for (int i = 0; i < visibleParts.length; i++) {
				visibleParts[i].activate();
			}
		} else {
			for (int i = 0; i < visibleParts.length; i++) {
				visibleParts[i].deactivate();
			}
		}
	}

	@Override
	protected int setSetupMode(boolean mode) {
		//
		// Note that setup is only done on the original scene graph;
		// cloned features are exempt from setup/unsetup. This is
		// enforced by only doing setup on the original scene graph,
		// and by only setting cloned features into a scene graph by
		// virtue of a group.
		//
		if (mode) {
			numSetupChecked = 0;
			int num = 0;
			for (int i = 0; i < parts.length; i++) {
				num += parts[i].setup();
			}
			return num;
		} else {
			for (int i = 0; i < parts.length; i++) {
				parts[i].unsetup();
			}
			return 0;
		}
	}

	@Override
	public boolean needsMoreSetup() {
		//
		// See note about cloned features in setSetupMode()
		//
		while (numSetupChecked < parts.length) {
			if (parts[numSetupChecked].needsMoreSetup()) {
				return true;
			}
			numSetupChecked++;
			// Once a part doesn't need more setup, it will never go
			// back to needing setup until we call unsetup() then
			// setup(). numSetupChecked is re-set to 0 just before
			// callin setup() on our part, so this is safe. Note
			// that the contract of Feature requires that setup()
			// be called before needsMoreSetup() is consulted.
			//
			// This optimization helps speed the calculation of
			// needsMoreSetup() in the case where a group or an
			// assembly is the child of multiple parts of an assembly.
			// With this optimization, a potential O(n^2) is turned
			// into O(n) (albeit typically with a small n).
		}
		return false;
	}

	@Override
	public void markAsChanged() {
		for (int i = 0; i < visibleParts.length; i++) {
			visibleParts[i].markAsChanged();
		}
	}

	@Override
	public void paintFrame(Graphics gc) {
		for (int i = 0; i < visibleParts.length; i++) {
			visibleParts[i].paintFrame(gc);
		}
	}

	@Override
	public void paintDone() {
		for (int i = 0; i < visibleParts.length; i++) {
			visibleParts[i].paintDone();
		}
	}

	@Override
	public void nextFrame() {
		for (int i = 0; i < visibleParts.length; i++) {
			visibleParts[i].nextFrame();
		}
	}

}
